append_uint32 (output, type);
}
+static BroadwayNode *
+lookup_old_node (GHashTable *old_node_lookup,
+ guint32 id)
+{
+ if (old_node_lookup)
+ return g_hash_table_lookup (old_node_lookup, GINT_TO_POINTER (id));
+
+ return NULL;
+}
+
/***********************************
* This outputs the tree to the client, while at the same time diffing
static void
append_node (BroadwayOutput *output,
BroadwayNode *node,
- BroadwayNode *old_node,
- gboolean all_parents_are_kept)
+ GHashTable *old_node_lookup)
{
guint32 i;
+ BroadwayNode *reused_node;
append_node_depth++;
- if (old_node != NULL && broadway_node_equal (node, old_node))
+ reused_node = lookup_old_node (old_node_lookup, node->id);
+ if (reused_node)
{
- if (broadway_node_deep_equal (node, old_node))
- {
- append_type (output, BROADWAY_NODE_KEEP_ALL, node);
- goto out;
- }
-
- if (all_parents_are_kept)
- {
- append_type (output, BROADWAY_NODE_KEEP_THIS, node);
- append_uint32 (output, node->n_children);
- for (i = 0; i < node->n_children; i++)
- append_node (output, node->children[i],
- i < old_node->n_children ? old_node->children[i] : NULL,
- TRUE);
-
- goto out;
- }
+ append_type (output, BROADWAY_NODE_REUSE, node);
+ append_uint32 (output, node->id);
+ }
+ else
+ {
+ append_type (output, node->type, node);
+ append_uint32 (output, node->id);
+ for (i = 0; i < node->n_data; i++)
+ append_uint32 (output, node->data[i]);
+ for (i = 0; i < node->n_children; i++)
+ append_node (output,
+ node->children[i],
+ old_node_lookup);
}
- append_type (output, node->type, node);
- for (i = 0; i < node->n_data; i++)
- append_uint32 (output, node->data[i]);
- for (i = 0; i < node->n_children; i++)
- append_node (output,
- node->children[i],
- (old_node != NULL && i < old_node->n_children) ? old_node->children[i] : NULL,
- FALSE);
-
- out:
append_node_depth--;
}
+
+static void
+append_node_ops (BroadwayOutput *output,
+ BroadwayNode *node,
+ BroadwayNode *old_node,
+ GHashTable *old_node_lookup)
+{
+ GPtrArray *to_remove = g_ptr_array_new ();
+ guint32 i;
+
+ if (old_node != NULL)
+ g_ptr_array_add (to_remove, old_node);
+
+ append_uint32 (output, BROADWAY_NODE_OP_APPEND_NODE);
+ append_uint32 (output, 0); /* Parent */
+
+ append_node(output, node, old_node_lookup);
+
+ for (i = 0; i < to_remove->len; i++)
+ {
+ BroadwayNode *removed_node = g_ptr_array_index (to_remove, i);
+ if (!removed_node->used)
+ {
+ // TODO: Use an array of nodes instead
+ append_uint32 (output, BROADWAY_NODE_OP_REMOVE_NODE);
+ append_uint32 (output, old_node->id);
+ }
+ }
+ g_ptr_array_unref (to_remove);
+}
+
+
void
broadway_output_surface_set_nodes (BroadwayOutput *output,
int id,
BroadwayNode *root,
- BroadwayNode *old_root)
+ BroadwayNode *old_root,
+ GHashTable *old_node_lookup)
{
gsize size_pos, start, end;
- /* Early return if nothing changed */
- if (old_root != NULL &&
- broadway_node_deep_equal (root, old_root))
- return;
+ /* Mark old nodes as unused, so we can use them once */
+ if (old_root)
+ broadway_node_mark_deep_used (old_root, FALSE);
+ /* Mark new nodes as used, causing any shared nodes to be marked as
+ unused. This way we won't accidentally reuse them for something
+ else. However, this means that is safe to reuse nodes marked as
+ used for the specific case of where the id matches. */
+ broadway_node_mark_deep_used (root, TRUE);
write_header (output, BROADWAY_OP_SET_NODES);
#ifdef DEBUG_NODE_SENDING
g_print ("====== node tree for %d =======\n", id);
#endif
- append_node (output, root, old_root, TRUE);
+ append_node_ops (output, root, old_root, old_node_lookup);
end = output->buf->len;
patch_uint32 (output, (end - start) / 4, size_pos);
}
const BROADWAY_NODE_SHADOW = 8;
const BROADWAY_NODE_OPACITY = 9;
const BROADWAY_NODE_CLIP = 10;
-const BROADWAY_NODE_KEEP_ALL = 11;
-const BROADWAY_NODE_KEEP_THIS = 12;
-const BROADWAY_NODE_TRANSFORM = 13;
-const BROADWAY_NODE_DEBUG = 14;
-const BROADWAY_NODE_REUSE = 15;
+const BROADWAY_NODE_TRANSFORM = 11;
+const BROADWAY_NODE_DEBUG = 12;
+const BROADWAY_NODE_REUSE = 13;
+
+const BROADWAY_NODE_OP_APPEND_NODE = 0;
+const BROADWAY_NODE_OP_REMOVE_NODE = 1;
const BROADWAY_OP_GRAB_POINTER = 'g';
const BROADWAY_OP_UNGRAB_POINTER = 'u';
const DISPLAY_OP_REPLACE_CHILD = 0;
const DISPLAY_OP_APPEND_CHILD = 1;
-const DISPLAY_OP_APPEND_ROOT = 2;
-const DISPLAY_OP_SHOW_SURFACE = 3;
-const DISPLAY_OP_HIDE_SURFACE = 4;
-const DISPLAY_OP_DELETE_NODE = 5;
-const DISPLAY_OP_MOVE_NODE = 6;
-const DISPLAY_OP_RESIZE_NODE = 7;
-const DISPLAY_OP_RESTACK_SURFACES = 8;
-const DISPLAY_OP_DELETE_SURFACE = 9;
+const DISPLAY_OP_INSERT_AFTER_CHILD = 2;
+const DISPLAY_OP_APPEND_ROOT = 3;
+const DISPLAY_OP_SHOW_SURFACE = 4;
+const DISPLAY_OP_HIDE_SURFACE = 5;
+const DISPLAY_OP_DELETE_NODE = 6;
+const DISPLAY_OP_MOVE_NODE = 7;
+const DISPLAY_OP_RESIZE_NODE = 8;
+const DISPLAY_OP_RESTACK_SURFACES = 9;
+const DISPLAY_OP_DELETE_SURFACE = 10;
// GdkCrossingMode
const GDK_CROSSING_NORMAL = 0;
surface.transientParent = 0;
surface.visible = false;
surface.imageData = null;
+ surface.old_nodes = {};
var div = document.createElement('div');
div.surface = surface;
moveToHelper(surface, 0);
}
-function TransformNodes(node_data, div, display_commands) {
+function TransformNodes(node_data, div, old_nodes, display_commands) {
this.node_data = node_data;
this.display_commands = display_commands;
this.data_pos = 0;
this.div = div;
this.outstanding = 1;
+ this.old_nodes = old_nodes;
+ this.new_nodes = {};
}
TransformNodes.prototype.decode_uint32 = function() {
div.style["border-bottom-left-radius"] = args(px(rrect.sizes[3].width), px(rrect.sizes[3].height));
}
-TransformNodes.prototype.insertNode = function(parent, posInParent, oldNode)
+function addOldIds(node, node_map) {
+ node_map[node.node_id] = node;
+
+ var child = node.firstChild;
+ while (child) {
+ addOldIds(child, node_map);
+ child = child.nextSibling;
+ }
+}
+
+
+TransformNodes.prototype.insertNode = function(parent, previousSibling, is_toplevel)
{
var type = this.decode_uint32();
+ var id = this.decode_uint32();
var newNode = null;
-
- // We need to dup this because as we reuse children the original order is lost
- var oldChildren = [];
- if (oldNode) {
- for (var i = 0; i < oldNode.children.length; i++)
- oldChildren[i] = oldNode.children[i];
- }
+ var oldNode = null;
switch (type)
{
+ /* Reuse divs from last frame */
+ case BROADWAY_NODE_REUSE:
+ {
+ oldNode = this.old_nodes[id];
+ }
+ break;
/* Leaf nodes */
case BROADWAY_NODE_TEXTURE:
div.style["transform"] = transform_string;
div.style["transform-origin"] = "0px 0px";
- this.insertNode(div, -1, oldChildren[0]);
+ this.insertNode(div, null, false);
newNode = div;
}
break;
div.style["position"] = "absolute";
set_rect_style(div, rect);
div.style["overflow"] = "hidden";
- this.insertNode(div, -1, oldChildren[0]);
+ this.insertNode(div, null, false);
newNode = div;
}
break;
div.style["position"] = "absolute";
set_rrect_style(div, rrect);
div.style["overflow"] = "hidden";
- this.insertNode(div, -1, oldChildren[0]);
+ this.insertNode(div, null, false);
newNode = div;
}
break;
div.style["top"] = px(0);
div.style["opacity"] = opacity;
- this.insertNode(div, -1, oldChildren[0]);
+ this.insertNode(div, null, false);
newNode = div;
}
break;
div.style["top"] = px(0);
div.style["filter"] = filters;
- this.insertNode(div, -1, oldChildren[0]);
+ this.insertNode(div, null, false);
newNode = div;
}
break;
var str = this.decode_string();
var div = document.createElement('div');
div.setAttribute('debug', str);
- this.insertNode(div, -1, oldChildren[0]);
+ this.insertNode(div, null, false);
newNode = div;
}
break;
{
var div = document.createElement('div');
var len = this.decode_uint32();
+ var lastChild = null;
for (var i = 0; i < len; i++) {
- this.insertNode(div, -1, oldChildren[i]);
+ lastChild = this.insertNode(div, lastChild, false);
}
newNode = div;
}
break;
- case BROADWAY_NODE_KEEP_ALL:
- {
- if (!oldNode)
- alert("KEEP_ALL with no oldNode");
-
- if (oldNode.parentNode != parent)
- newNode = oldNode;
- else
- newNode = null;
- }
- break;
-
- case BROADWAY_NODE_KEEP_THIS:
- {
- if (!oldNode)
- alert("KEEP_THIS with no oldNode ");
-
- /* We only get keep-this if all parents were kept, check this */
- if (oldNode.parentNode != parent)
- alert("Got KEEP_THIS for non-kept parent");
-
- var len = this.decode_uint32();
- var i;
-
- for (i = 0; i < len; i++) {
- this.insertNode(oldNode, i,
- oldChildren[i]);
- }
-
- /* Remove children that are after the new length */
- for (i = oldChildren.length - 1; i > len - 1; i--)
- this.display_commands.push([DISPLAY_OP_DELETE_NODE, oldChildren[i]]);
-
- /* NOTE: No need to modify the parent, we're keeping this node as is */
- newNode = null;
- }
- break;
-
default:
alert("Unexpected node type " + type);
}
if (newNode) {
- if (posInParent >= 0 && parent.children[posInParent])
- this.display_commands.push([DISPLAY_OP_REPLACE_CHILD, parent, newNode, parent.children[posInParent]]);
- else
+ newNode.node_id = id;
+ this.new_nodes[id] = newNode;
+ if (is_toplevel)
this.display_commands.push([DISPLAY_OP_APPEND_CHILD, parent, newNode]);
+ else // Here it is safe to display directly because we have not added the toplevel to the document yet
+ parent.appendChild(newNode);
+ return newNode;
+ } else if (oldNode) {
+ addOldIds(oldNode, this.new_nodes);
+ // This must be delayed until display ops, because it will remove from the old parent
+ this.display_commands.push([DISPLAY_OP_INSERT_AFTER_CHILD, parent, previousSibling, oldNode]);
+ return oldNode;
}
}
TransformNodes.prototype.execute = function(display_commands)
{
- var div = this.div;
- this.insertNode(div, 0, div.firstChild, display_commands);
- if (this.data_pos != this.node_data.byteLength)
- alert ("Did not consume entire array (len " + this.node_data.byteLength + ")");
-}
+ var root = this.div;
+ while (this.data_pos < this.node_data.byteLength) {
+ var op = this.decode_uint32();
+
+ switch (op) {
+ case BROADWAY_NODE_OP_APPEND_NODE:
+ var parentId = this.decode_uint32();
+ var parent;
+ if (parentId == 0)
+ parent = root;
+ else
+ parent = this.old_nodes[parentId];
+ this.insertNode(parent, null, true);
+ break;
+ case BROADWAY_NODE_OP_REMOVE_NODE:
+ var removeId = this.decode_uint32();
+ var remove = this.old_nodes[removeId];
+ this.display_commands.push([DISPLAY_OP_DELETE_NODE, remove]);
+ break;
+ }
+ }
+}
function cmdGrabPointer(id, ownerEvents)
{
function handleDisplayCommands(display_commands)
{
- var div;
+ var div, parent;
var len = display_commands.length;
for (var i = 0; i < len; i++) {
var cmd = display_commands[i];
case DISPLAY_OP_APPEND_CHILD:
cmd[1].appendChild(cmd[2]);
break;
+ case DISPLAY_OP_INSERT_AFTER_CHILD:
+ parent = cmd[1];
+ var afterThis = cmd[2];
+ div = cmd[3];
+ if (afterThis == null) // First
+ parent.insertBefore(div, parent.firstChild);
+ else
+ parent.insertBefore(div, afterThis.nextSibling);
+ break;
case DISPLAY_OP_APPEND_ROOT:
document.body.appendChild(cmd[1]);
break;
var node_data = cmd.get_nodes ();
surface = surfaces[id];
- var transform_nodes = new TransformNodes (node_data, surface.div, display_commands);
+ var transform_nodes = new TransformNodes (node_data, surface.div, surface.old_nodes, display_commands);
transform_nodes.execute();
+ surface.old_nodes = transform_nodes.new_nodes;
}
break;